【SwiftUI】iOS 15からの@FocusStateを使用して画面タップでキーボードを閉じる方法
iOS 15から使用できるプロパティラッパー@FocusState
を使用してキーボードを閉じる方法を調べました。今回、画面をタップした時に@FocusState
を付与した値を更新してキーボードを閉じる方法を記載しています。
環境
- Xcode 13.2.1
準備
TextField
を二つ配置したView
を準備しました。
import SwiftUI struct ContentView: View { @State private var titleText = "" @State private var messageText = "" var body: some View { VStack { TextField("タイトル", text: $titleText) .padding() .border(.black) .padding() TextField("メッセージ", text: $messageText) .padding() .border(.black) .padding() } } }
まだこの時点では、TextFieldにフォーカスが当たった時に出てくるキーボードは改行ボタンを押した時にしか閉じることが出来ません。
@FocusStateを使用してキーボードを閉じる
@FocusState
@FocusStateとは、フォーカスが変更があった時にSwiftUIが更新する値を読み書きできるプロパティラッパーです。
@frozen @propertyWrapper struct FocusState<Value> where Value : Hashable
@FocusStateを使用する
フォーカスが当たるTextField
を判断するためのenum
を作成します。@FocusState
の定義にもある通り、Value
はHashable
である必要がある為、準拠しています。
enum Field: Hashable { case title case message }
@FocusState
を付与した値をnil
にするとキーボードが閉じてくれるのでオプショナルにしています。
@FocusState private var focusedField: Field?
TextFieldにフォーカスが当たった時に@FocusStateを更新
TextField
にfocused
モディファイアを追加します。
第一引数には@FocusState
の値を渡し、第二引数には今回はどのfocusedField
を指しているのかを渡しています。
var body: some View { VStack { TextField("タイトル", text: $titleText) .padding() .border(.black) .padding() .focused($focusedField, equals: .title) TextField("メッセージ", text: $messageText) .padding() .border(.black) .padding() .focused($focusedField, equals: .message) } }
画面タップでキーボードを閉じる
onTapGuesture
でfocuedField
の値をnil
にすると画面をタップ時にキーボードを閉じる事が出来ます。
このままだとタップ領域が狭い為、VStack
にframe
とcontentShape
を追加してタップ領域を広げています。
VStack { // 省略 } .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height) .contentShape(RoundedRectangle(cornerRadius: 10)) .onTapGesture { focusedField = nil }
これで画面タップでキーボードが閉じれるようになりました。
キーボードが一度閉じてしまう問題
画面タップでキーボードを閉じれるようになったのですが、一度キーボードを出した状態で別のTextField
をタップした時に一度キーボードを閉じなければならない状態になってしまいました。
これだとユーザー体験的には気持ち良くはないと思います。別のTextField
に移動した際はキーボードを出した状態にする為に各TextField
にもonTapGUesture
を追加して、focusedField
に値を入れるようにします。
TextField("タイトル", text: $titleText) .padding() .border(.black) .padding() .focused($focusedField, equals: .title) .onTapGesture { focusedField = .title } TextField("メッセージ", text: $messageText) .padding() .border(.black) .padding() .focused($focusedField, equals: .message) .onTapGesture { focusedField = .message }
これで別のTextField
に移った際にはキーボードが出た状態を保持することができるようになりました。
コード全体
今回のContentView
のコード全体になります。
import SwiftUI struct ContentView: View { enum Field: Hashable { case title case message } @State private var titleText = "" @State private var messageText = "" @FocusState private var focusedField: Field? var body: some View { VStack { TextField("タイトル", text: $titleText) .padding() .border(.black) .padding() .focused($focusedField, equals: .title) .onTapGesture { focusedField = .title } TextField("メッセージ", text: $messageText) .padding() .border(.black) .padding() .focused($focusedField, equals: .message) .onTapGesture { focusedField = .message } } .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height) .contentShape(RoundedRectangle(cornerRadius: 10)) .onTapGesture { focusedField = nil } } }
おわりに
@FocusState
を使った良い画面タップでのキーボードの閉じ方、別TextField
に移行した時にキーボードを表示したままにするやり方をご存知の方いましたら教えていただけますと嬉しいです。